//
// Created by luosong on 15/8/28.
// Copyright (c) 2015 CHANJET. All rights reserved.
//

#import "CSPIdentityService.h"
#import "CSPTask.h"
#import "CSPNetworkingRequest.h"
#import "CSPCategory.h"
#import "CSPRequestRetryHandler.h"
#import "CSPJSONResponseSerializer.h"
#import "CSPURLRequestSerialization.h"
#import "CSPUICKeyChainStore.h"
#import "CSPTask+PromiseLike.h"

NSString *const CSPCredentialsProviderKeychainAccessKeyId = @"accessKey";
NSString *const CSPCredentialsProviderKeychainSecretAccessKey = @"secretKey";
NSString *const CSPCredentialsProviderKeychainSessionToken = @"sessionKey";
NSString *const CSPCredentialsProviderKeychainExpiration = @"expiration";
NSString *const CSPCredentialsProviderKeychainLogins = @"logins";
NSString *const CSPCredentialsProviderKeychainExtra = @"extra";

NSString *const CSPCredentialsAgentBaseUrl = @"http://i.chanjet.com/chanjet/customer/";
NSString *const CSPCredentialsCIABaseUrl = @"http://cia.chanapp.chanjet.com/";

#pragma mark - CSPIdentityLogoutRequest

#pragma mark - CSPIdentityExtra

@interface CSPIdentityExtra ()
@end

@implementation CSPIdentityExtra
@end

#pragma mark - CSPIdentityStore

@interface CSPIdentityStore ()

@property (nonatomic, strong) CSPUICKeyChainStore *keychain;

@property (nonatomic, strong) NSDictionary *logins;
@property (nonatomic, strong) NSString *accessKey;
@property (nonatomic, strong) NSString *secretKey;
@property (nonatomic, strong) NSString *sessionKey;
@property (nonatomic, strong) NSDate *expiration;
@property (nonatomic, strong) CSPIdentityExtra *extra;

@end

@implementation CSPIdentityStore

@synthesize accessKey=_accessKey;
@synthesize secretKey=_secretKey;
@synthesize sessionKey=_sessionKey;
@synthesize expiration=_expiration;
@synthesize logins=_logins;
@synthesize extra=_extra;

- (instancetype)init {
    self = [super init];
    if (self) {
        _keychain = [CSPUICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@.%@", [NSBundle mainBundle].bundleIdentifier, [CSPIdentityStore class]]];
    }
    return self;
}

- (void)clearKeychain {
    @synchronized(self) {
        [self clearCredentials];
    }
}

- (void)clearCredentials {
    @synchronized(self) {
        self.accessKey = nil;
        self.secretKey = nil;
        self.sessionKey = nil;
        self.expiration = nil;
        self.logins = nil;
        self.extra = nil;
    }
}

- (CSPIdentityExtra *)extra {
    @synchronized(self) {
        if (!_extra) {
            id jsonObj = [self.keychain[CSPCredentialsProviderKeychainExtra] csp_parseJSON];
            if (jsonObj) {
                _extra = [CSPMTLJSONAdapter modelOfClass:[CSPIdentityExtra class] fromJSONDictionary:jsonObj error:nil];
            }
        }
        return _extra;
    }
}

- (void)setExtra:(CSPIdentityExtra *)extra {
    @synchronized(self) {
        _extra = extra;
        self.keychain[CSPCredentialsProviderKeychainExtra] = [[extra dictionaryValue] csp_toJSONString];
    }
}

- (NSDictionary *)logins {
    @synchronized(self) {
        if (!_logins) {
            _logins = [self.keychain[CSPCredentialsProviderKeychainLogins] csp_parseJSON];
        }
        return _logins;
    }
}

- (void)setLogins:(NSDictionary *)logins {
    @synchronized(self) {
        _logins = logins;
        self.keychain[CSPCredentialsProviderKeychainLogins] = [_logins csp_toJSONString];
    }
}

- (NSString *)accessKey {
    @synchronized(self) {
        if (!_accessKey) {
            _accessKey = self.keychain[CSPCredentialsProviderKeychainAccessKeyId];
        }
        return _accessKey;
    }
}

- (NSString *)secretKey {
    @synchronized(self) {
        if (!_secretKey) {
            _secretKey = self.keychain[CSPCredentialsProviderKeychainSecretAccessKey];
        }
        return _secretKey;
    }
}

- (NSString *)sessionKey {
    @synchronized(self) {
        if (!_sessionKey) {
            _sessionKey = self.keychain[CSPCredentialsProviderKeychainSessionToken];
        }
        return _sessionKey;
    }
}

- (NSDate *)expiration {
    @synchronized(self) {
        if (!_expiration) {
            NSString *expirationString = self.keychain[CSPCredentialsProviderKeychainExpiration];
            if (expirationString) {
                _expiration = [NSDate dateWithTimeIntervalSince1970:[expirationString doubleValue]];
            }
        }
        return _expiration;
    }
}

- (void)setAccessKey:(NSString *)accessKey {
    @synchronized(self) {
        _accessKey = accessKey;
        self.keychain[CSPCredentialsProviderKeychainAccessKeyId] = accessKey;
    }
}

- (void) setSecretKey:(NSString *)secretKey {
    @synchronized(self) {
        _secretKey = secretKey;
        self.keychain[CSPCredentialsProviderKeychainSecretAccessKey] = secretKey;
    }
}

- (void) setSessionKey:(NSString *)sessionKey {
    @synchronized(self) {
        _sessionKey = sessionKey;
        self.keychain[CSPCredentialsProviderKeychainSessionToken] = sessionKey;
    }
}

- (void) setExpiration:(NSDate *)expiration {
    @synchronized(self) {
        _expiration = expiration;
        if (expiration) {
            self.keychain[CSPCredentialsProviderKeychainExpiration] = [NSString stringWithFormat:@"%f", [expiration timeIntervalSince1970]];
        }
        else {
            self.keychain[CSPCredentialsProviderKeychainExpiration] = nil;
        }
    }
}

@end

#pragma mark - CSPIdentityResponseSerializer

@interface CSPIdentityResponseSerializer : CSPJSONResponseSerializer
@end

@implementation CSPIdentityResponseSerializer

- (id)responseObjectForResponse:(NSHTTPURLResponse *)response originalRequest:(NSURLRequest *)originalRequest
                 currentRequest:(NSURLRequest *)currentRequest data:(id)data error:(NSError *__autoreleasing *)error {
    id resultObj = [super responseObjectForResponse:response originalRequest:originalRequest currentRequest:currentRequest data:data error:error];

    if (!*error && [resultObj isKindOfClass:[NSDictionary class]]) {
        if ([resultObj[@"errorCode"] integerValue] != 0) {
            NSString *message = resultObj[@"errorMessage"];
            *error = [NSError errorWithDomain:CSPGeneralErrorDomain
                                         code:[resultObj[@"errorCode"] integerValue]
                                     userInfo:@{NSLocalizedDescriptionKey : message? message: [NSNull null]}];
        }
        else {
            if (self.outputClass) {
                resultObj = [CSPMTLJSONAdapter modelOfClass:self.outputClass
                                         fromJSONDictionary:resultObj
                                                      error:error];
            }
        }
    }

    return resultObj;
}

@end

#pragma mark - CSPIdentityRequestRetryHandler

@interface CSPIdentityRequestRetryHandler : CSPRequestRetryHandler
@end

@implementation CSPIdentityRequestRetryHandler

- (CSPRequestRetryType)shouldRetry:(uint32_t)currentRetryCount
                             response:(NSHTTPURLResponse *)response
                                 data:(NSData *)data
                                error:(NSError *)error {
    CSPRequestRetryType retryType = [super shouldRetry:currentRetryCount
                                                 response:response
                                                     data:data
                                                    error:error];
    if(retryType == CSPRequestRetryTypeShouldNotRetry
            && [error.domain isEqualToString:CSPIdentityErrorDomain]
            && currentRetryCount < self.maxRetryCount) {
        switch (error.code) {
            case CSPIdentityErrorIncompleteSignature:
            case CSPIdentityErrorInvalidClientTokenId:
            case CSPIdentityErrorMissingAuthenticationToken:
                retryType = CSPRequestRetryTypeShouldRefreshCredentialsAndRetry;
                break;

            default:
                break;
        }
    }

    return retryType;
}

@end

#pragma mark - CSPIdentityService

@interface CSPRequest()

@property (nonatomic, strong) CSPNetworkingRequest *internalRequest;

@end

@interface CSPIdentityService()
@property (nonatomic, strong) CSPNetworking *networking;
@end

@implementation CSPIdentityService {
    CSPIdentityStore * _identityStore;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _identityStore = [[CSPIdentityStore alloc] init];

        _configuration = [CSPRequestConfig alloc].init;
        _configuration.retryHandler = [[CSPIdentityRequestRetryHandler alloc] initWithMaximumRetryCount:3];
        _configuration.headers = @{@"Content-Type" : @"application/x-www-form-urlencoded; charset=utf-8"};

        _networking = [[CSPNetworking alloc] initWithConfiguration:_configuration];
    }

    return self;
}

- (CSPIdentityStore *)identityStore {
    if ([_identityStore.accessKey length] > 0) {
        return _identityStore;
    }
    return nil;
}


- (CSPTask *)invokeRequest:(CSPRequest *)request
                HTTPMethod:(CSPHTTPMethod)HTTPMethod
                 URLString:(NSString *) URLString
              targetPrefix:(NSString *)targetPrefix
             operationName:(NSString *)operationName
               outputClass:(Class)outputClass {
    @autoreleasepool {
        if (!request) {
            request = [CSPRequest new];
        }
        CSPNetworkingRequest * networkingRequest = request.internalRequest;
        networkingRequest.parameters = [[CSPMTLJSONAdapter JSONDictionaryFromModel:request] csp_removeNullValues];

        NSMutableDictionary *parameters = [NSMutableDictionary new];
        __block NSString *blockSafeURLString = [URLString copy];

        [networkingRequest.parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            NSString *stringToFind = [NSString stringWithFormat:@"{%@}", key];
            if ([blockSafeURLString rangeOfString:stringToFind].location == NSNotFound) {
                parameters[key] = obj;
            } else {
                blockSafeURLString = [blockSafeURLString stringByReplacingOccurrencesOfString:stringToFind
                                                                                   withString:obj];
            }
        }];
        networkingRequest.parameters = parameters;

        NSMutableDictionary *headers = [NSMutableDictionary new];
        headers[@"X-Chanjet-Target"] = [NSString stringWithFormat:@"%@.%@", targetPrefix, operationName];

        networkingRequest.headers = headers;
        networkingRequest.URLString = blockSafeURLString;
        networkingRequest.HTTPMethod = HTTPMethod;
        networkingRequest.requestSerializer = [CSPQueryStringRequestSerializer alloc].init;
        networkingRequest.responseSerializer = [[CSPIdentityResponseSerializer alloc] initWithOutputClass:outputClass];

        return [self.networking sendRequest:networkingRequest];
    }
}

- (CSPTask *)applySignupCode:(CSPIdentityApplySignupCodeRequest *)request {
    CSPTask *task = [self invokeRequest:request HTTPMethod:CSPHTTPMethodPOST
                     URLString:[CSPCredentialsAgentBaseUrl stringByAppendingString:@"mobile/checkMobileAndRegCode"]
                  targetPrefix:[[self class] description] operationName:@"applySignupCodeWithMobile"
                                                            outputClass:[CSPIdentityApplySignupCodeResponse class]];
    return [task continueWithBlock:^id(CSPTask *task) {
        if (! task.error) {
            /**
            * 由于 CIA 平台接口不规范，我们需要对返回结果做规范化的处理。
            * 这个规范化的处理应该放到我们的应用服务器层。
            */
            CSPIdentityApplySignupCodeResponse *response = task.result;

            if (response.errorCode == 10002) {
                NSError *error = [NSError errorWithDomain:CSPIdentityErrorDomain
                                                     code:CSPIdentityErrorInvalidParameter
                                                 userInfo:nil];
                return [CSPTask taskWithError:error];
            }
            else if (response.exist) {
                NSError *error = [NSError errorWithDomain:CSPIdentityErrorDomain
                                                     code:CSPIdentitySignupAccountExists
                                                 userInfo:nil];
                return [CSPTask taskWithError:error];
            }

            return nil;
        }
        return task;
    }];
}

- (CSPTask *)loginWithAccountPassword:(CSPIdentityLoginWithAccountRequest *)request {
    CSPTask * task =  [self invokeRequest:request HTTPMethod:CSPHTTPMethodPOST
                                URLString:[CSPCredentialsAgentBaseUrl stringByAppendingString:@"mobile/login"]
                             targetPrefix:nil operationName:nil outputClass:[CSPIdentityGetCredentialsResponse class]];

    return [task continueWithBlock:^id(CSPTask *task) {
        if (! task.error) {
            // 保存
            NSDictionary * logins = @{
                    @"account": request.account,
                    @"password": request.password
            };

            CSPIdentityGetCredentialsResponse * response = task.result;

            _identityStore.logins = logins; //存储用户名和密码
            _identityStore.accessKey = response.accessToken;    //存储授权key
            _identityStore.sessionKey = response.refreshToken;
            //明显这样是不对的，因为服务器时间和本地时间不一致.
            _identityStore.expiration = [NSDate dateWithTimeInterval:response.expiration sinceDate:[NSDate date]];
            _identityStore.extra = response.extra;

            task = [CSPTask taskWithResult:_identityStore];
        }
        else {
            /**
            * 将 CIA 提供的接口错误转换为我们 SDK 的错误类型。
            * TODO: 这个转换应该在我们的应用服务器上进行。
            */
        }

        return task;
    }];
}

- (CSPTask *)logout {
    if ([_identityStore.accessKey length] == 0) {
        return [CSPTask taskWithResult:nil];
    }

    CSPIdentitySignoutRequest *request = [CSPIdentitySignoutRequest new];
    request.accessToken = _identityStore.accessKey;
    request.clientId = @"newapp";
    request.redirectUri = @"";

    CSPTask * task =  [self invokeRequest:request HTTPMethod:CSPHTTPMethodPOST
                                URLString:[CSPCredentialsCIABaseUrl stringByAppendingString:@"internal_api/logout"]
                             targetPrefix:[[self class] description] operationName:@"Logout"
                                                                       outputClass:nil];
    return [task continueWithBlock:^id(CSPTask *task) {
        /**
        *  这里其实是存在问题的，CIA提供的 API 成功后没有直接返回 200，而是进行了redirect
        *  按道理，提供的rest接口不应该进行redirect。
        *  现在我们暂时认为是成功的退出登录了。
        *  TODO: 这个处理应该放到我们的应用服务器上进行。
        */
        if (task.error && task.error.code == CSPGeneralErrorUnknown) {
            return nil;
        }
        return task;
    }];
}

- (CSPTask *)refreshToken {
    NSDictionary *logins = [self parseJSON:_identityStore.logins];

    if (logins.count == 0 || [_identityStore.accessKey length] == 0) {
        NSError *error = [NSError errorWithDomain:CSPIdentityErrorDomain
                                             code:CSPIdentityErrorMissingAuthenticationToken
                                         userInfo:nil];
        return [CSPTask taskWithError:error];
    }

    CSPIdentityLoginWithAccountRequest * request = [[CSPIdentityLoginWithAccountRequest alloc] init];
    request.account = logins[@"account"];
    request.password = logins[@"password"];

    return [self loginWithAccountPassword:request];
}

- (NSDictionary *)parseJSON:(NSString *)json {
    if ([json length]==0)
        return nil;
    return [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
}


@end